package spnego

import (
	
	
	
	
	

	
	
	
	
	
	
	
	
	
	
	
)

// GSSAPI KRB5 MechToken IDs.
const (
	TOK_ID_KRB_AP_REQ = "0100"
	TOK_ID_KRB_AP_REP = "0200"
	TOK_ID_KRB_ERROR  = "0300"
)

// KRB5Token context token implementation for GSSAPI.
type KRB5Token struct {
	OID      asn1.ObjectIdentifier
	tokID    []byte
	APReq    messages.APReq
	APRep    messages.APRep
	KRBError messages.KRBError
	settings *service.Settings
	context  context.Context
}

// Marshal a KRB5Token into a slice of bytes.
func ( *KRB5Token) () ([]byte, error) {
	// Create the header
	,  := asn1.Marshal(.OID)
	 = append(, .tokID...)
	var  []byte
	var  error
	switch hex.EncodeToString(.tokID) {
	case TOK_ID_KRB_AP_REQ:
		,  = .APReq.Marshal()
		if  != nil {
			return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", )
		}
	case TOK_ID_KRB_AP_REP:
		return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5")
	case TOK_ID_KRB_ERROR:
		return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5")
	}
	if  != nil {
		return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", )
	}
	 = append(, ...)
	return asn1tools.AddASNAppTag(, 0), nil
}

// Unmarshal a KRB5Token.
func ( *KRB5Token) ( []byte) error {
	var  asn1.ObjectIdentifier
	,  := asn1.UnmarshalWithParams(, &, fmt.Sprintf("application,explicit,tag:%v", 0))
	if  != nil {
		return fmt.Errorf("error unmarshalling KRB5Token OID: %v", )
	}
	if !.Equal(gssapi.OIDKRB5.OID()) {
		return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", .String(), gssapi.OIDKRB5.OID().String())
	}
	.OID = 
	if len() < 2 {
		return fmt.Errorf("krb5token too short")
	}
	.tokID = [0:2]
	switch hex.EncodeToString(.tokID) {
	case TOK_ID_KRB_AP_REQ:
		var  messages.APReq
		 = .Unmarshal([2:])
		if  != nil {
			return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", )
		}
		.APReq = 
	case TOK_ID_KRB_AP_REP:
		var  messages.APRep
		 = .Unmarshal([2:])
		if  != nil {
			return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", )
		}
		.APRep = 
	case TOK_ID_KRB_ERROR:
		var  messages.KRBError
		 = .Unmarshal([2:])
		if  != nil {
			return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", )
		}
		.KRBError = 
	}
	return nil
}

// Verify a KRB5Token.
func ( *KRB5Token) () (bool, gssapi.Status) {
	switch hex.EncodeToString(.tokID) {
	case TOK_ID_KRB_AP_REQ:
		, ,  := service.VerifyAPREQ(&.APReq, .settings)
		if  != nil {
			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: .Error()}
		}
		if ! {
			return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"}
		}
		.context = context.Background()
		.context = context.WithValue(.context, ctxCredentials, )
		return true, gssapi.Status{Code: gssapi.StatusComplete}
	case TOK_ID_KRB_AP_REP:
		// Client side
		// TODO how to verify the AP_REP - not yet implemented
		return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"}
	case TOK_ID_KRB_ERROR:
		if .KRBError.MsgType != msgtype.KRB_ERROR {
			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"}
		}
		return true, gssapi.Status{Code: gssapi.StatusUnavailable}
	}
	return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"}
}

// IsAPReq tests if the MechToken contains an AP_REQ.
func ( *KRB5Token) () bool {
	if hex.EncodeToString(.tokID) == TOK_ID_KRB_AP_REQ {
		return true
	}
	return false
}

// IsAPRep tests if the MechToken contains an AP_REP.
func ( *KRB5Token) () bool {
	if hex.EncodeToString(.tokID) == TOK_ID_KRB_AP_REP {
		return true
	}
	return false
}

// IsKRBError tests if the MechToken contains an KRB_ERROR.
func ( *KRB5Token) () bool {
	if hex.EncodeToString(.tokID) == TOK_ID_KRB_ERROR {
		return true
	}
	return false
}

// Context returns the KRB5 token's context which will contain any verify user identity information.
func ( *KRB5Token) () context.Context {
	return .context
}

// NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ
func ( *client.Client,  messages.Ticket,  types.EncryptionKey,  []int,  []int) (KRB5Token, error) {
	// TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client.
	var  KRB5Token
	.OID = gssapi.OIDKRB5.OID()
	,  := hex.DecodeString(TOK_ID_KRB_AP_REQ)
	.tokID = 

	,  := krb5TokenAuthenticator(.Credentials, )
	if  != nil {
		return , 
	}
	,  := messages.NewAPReq(
		,
		,
		,
	)
	if  != nil {
		return , 
	}
	for ,  := range  {
		types.SetFlag(&.APOptions, )
	}
	.APReq = 
	return , nil
}

// krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken
func krb5TokenAuthenticator( *credentials.Credentials,  []int) (types.Authenticator, error) {
	//RFC 4121 Section 4.1.1
	,  := types.NewAuthenticator(.Domain(), .CName())
	if  != nil {
		return , krberror.Errorf(, krberror.KRBMsgError, "error generating new authenticator")
	}
	.Cksum = types.Checksum{
		CksumType: chksumtype.GSSAPI,
		Checksum:  newAuthenticatorChksum(),
	}
	return , nil
}

// Create new authenticator checksum for kerberos MechToken
func newAuthenticatorChksum( []int) []byte {
	 := make([]byte, 24)
	binary.LittleEndian.PutUint32([:4], 16)
	for ,  := range  {
		if  == gssapi.ContextFlagDeleg {
			 := make([]byte, 28-len())
			 = append(, ...)
		}
		 := binary.LittleEndian.Uint32([20:24])
		 |= uint32()
		binary.LittleEndian.PutUint32([20:24], )
	}
	return 
}